/**
 ************************************************************************************************
 * 											wav test sig										*
 ************************************************************************************************
 * 
 * A set of classes for producing common audio test signals.
 * 
 * Copyright (c) 2014, Aaron L. Jones
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted 
 * provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 
 * conditions and the following disclaimer in the documentation and/or other materials provided 
 * with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
**/

#include "wts.h"

sig_type wts_args::signal_type = WTS_INVALID;
char* wts_args::filename = NULL;

/* assume default parameter values */
double wts_args::sample_rate	=	WTS_DEFAULT_SAMPLE_RATE;	
double wts_args::frequency		=	WTS_DEFAULT_FREQUENCY;
double wts_args::freq_lo		=	WTS_DEFAULT_FREQ_LO;
double wts_args::freq_hi		=	WTS_DEFAULT_FREQ_HI;
double wts_args::duration		=	WTS_DEFAULT_DURATION;
double wts_args::gain			=	WTS_DEFAULT_GAIN;

/********************************************************************************************
 * 										wav_test_stream										*
 ********************************************************************************************
 * 
**/

wav_test_stream::wav_test_stream(test_sig *_signal, const char *filename)
{
	w_mode = (wav_mode*) malloc(sizeof(*w_mode));
	if(!w_mode)
	{
		printf("unable to allocate wav_mode structure");
		/* @Todo: throw error */
	}
	
	/* initialize file writing parameters in wav_mode structure */
	w_mode->sample_rate	=	(uint32_t) wts_args::sample_rate;
	w_mode->bit_depth	=	WTS_OSC_BIT_DEPTH;
	w_mode->channelct	=	WTS_OSC_WAV_CHANNELS;
	
	/* open .WAV file for writing */
	w_file = wav_fopen(filename, w_mode);
	if(!w_file)
	{
		printf("unable to initialize wav_file structure");
		/* @Todo: throw error */
	}
	
	buflen = (uint32_t) (wts_args::duration * wts_args::sample_rate);
	buffer = (int16_t*) malloc(buflen * sizeof(int16_t));
	
	/* initialize sine wave look-up table */
	osc_init_sin_table();
	
	signal = _signal;
}

wav_test_stream::~wav_test_stream()
{
	if(w_mode)	free(w_mode);
	if(w_file)	wav_fclose(w_file);
	if(buffer)	free(buffer);
	if(signal)	delete signal;
}

void wav_test_stream::write_file()
{
	signal->block(buffer, buflen);
	wav_fwrite(buffer, buflen, w_file);
}

/********************************************************************************************
 * 											sin_wave										*
 ********************************************************************************************
 * 
**/

void osc_init_sin_table()
{
	int iii;
			
	/* fill sine wave look-up table */
	int32_t* sine_table = osc_sine_table;
	for(iii=0;iii<OSC_TABLE_LEN;iii++){								
		*sine_table++ = (int32_t) ( WTS_OSC_SIN_AMP * sin( (WTS_OSC_PI*2 * iii) / OSC_TABLE_LEN ) );
	}
}

sin_wave::sin_wave(double sample_rate, double frequency, double gain)
{
	amp = pow(10, (gain / 20.0) );
	
	/* initialize frequency register */
	freq_reg = (int32_t) ( (frequency / sample_rate) * (1 << OSC_PHASE_BITS) );
	
	phase_reg = 0;
}

void sin_wave::block(int16_t *buffer, uint32_t framect)
{
	int iii;
	
	for(iii=0; iii<framect; iii++)
	{
		/* update instantaneous phase register */
		phase_reg	+=	freq_reg;
		
		/* apply bit mask to phase register */
		phase_reg	=	(phase_reg & OSC_PHASE_MASK);
		
		/* locate memory index */
		lut_index	=	phase_reg >> (OSC_PHASE_BITS - OSC_MEM_BITS);
		
		/* get value from wave table and apply amplitude */
		*buffer++	= (int16_t) (osc_sine_table[lut_index] * amp);
	}
}

/********************************************************************************************
 * 											freq_swp										*
 ********************************************************************************************
 * 
**/

freq_swp::freq_swp(double sample_rate, double freq_lo, double freq_hi, double gain, double duration)
{
	amp = pow(10, (gain / 20.0) );
	
	fwarp = freq_lo / sample_rate;
	
	finc = (freq_hi - freq_lo) / (sample_rate * sample_rate * duration);
	
	phase_reg = 0;
}

void freq_swp::block(int16_t *buffer, uint32_t framect)
{
	int iii;
	
	for(iii=0; iii<framect; iii++)
	{
		/* update frequency register */
		freq_reg = (int32_t) ( fwarp * (1 << OSC_PHASE_BITS) );
		
		/* update instantaneous phase register */
		phase_reg	+=	freq_reg;
		
		/* apply bit mask to phase register */
		phase_reg	=	(phase_reg & OSC_PHASE_MASK);
		
		/* locate memory index */
		lut_index	=	phase_reg >> (OSC_PHASE_BITS - OSC_MEM_BITS);
		
		/* get value from wave table and apply amplitude */
		*buffer++	= (int16_t) (osc_sine_table[lut_index] * amp);
		
		/* increment instantaneous frequency */
		fwarp += finc;
	}
}

/********************************************************************************************
 * 											pr_noise										*
 ********************************************************************************************
 * 
**/


/** 
 * 
 * look-up table of primative polynomials for maximum length sequence generation
 * 
 * @Todo: this doesn't work... debug or omit
 *
**/
//int32_t pr_noise_gen_poly[WTS_MAX_LFSR_BITS + 1] = {	0x00000000,		/* INVALID 0-bits */
//														0x00000000,		/* INVALID 1-bit */
//														0x00000003,		/* 2-bits feedback polynomial: x^2 + x + 1 */
//														0x00000006,		/* 3-bits */
//														0x0000000C,		/* 4-bits */
//														0x00000014,		/* 5-bits */
//														0x00000030,		/* 6-bits */
//														0x00000060,		/* 7-bits */
//														0x000000B0,		/* 8-bits */
//														0x00000110,		/* 9-bits */
//														0x00000240,		/* 10-bits */
//														0x00000500,		/* 11-bits */
//														0x00000E08,		/* 12-bits */
//														0x00001C80,		/* 13-bits */
//														0x00003802,		/* 14-bits */
//														0x00006000,		/* 15-bits */
//														0x0000B400,		/* 16-bits */
//														0x00012000,		/* 17-bits */
//														0x00020400,		/* 18-bits */
//														0x00720000,		/* 19-bits */
//														0x00000000,		/* 20-bits */
//														0x00000000,		/* 21-bits */
//														0x00000000,		/* 22-bits */
//														0x00000000,		/* 23-bits */
//														0x00000000,		/* 24-bits */
//														0x00000000,		/* 25-bits */
//														0x00000000,		/* 26-bits */
//														0x00000000,		/* 27-bits */
//														0x00000000,		/* 28-bits */
//														0x00000000,		/* 29-bits */
//														0x00000000,		/* 30-bits */
//														0x00000000,		/* 31-bits */
//														0x00000000	};	/* 32-bits */
														
                                                        
														
pr_noise::pr_noise(double gain)
{
	amp = pow(10, (gain / 20.0) );
	
	/* set LFSR to initial state */
	shift_reg =	PR_NOISE_LFSR_INIT;
}

void pr_noise::block(int16_t *buffer, uint32_t framect)
{
	uint32_t ii, iii;
	
	for(ii=0; ii<framect; ii++)
	{
		shift_reg = (shift_reg >> 1) ^ ( -(shift_reg & PR_NOISE_LSB_MASK) & PR_NOISE_GEN_POLY );
		
		*buffer++ =  (( (int16_t) shift_reg  ) * amp);
	}
	
//	for(ii=0; ii<framect; ii++)
//	{
//		x_reg = shift_reg & PR_NOISE_GEN_POLY;
//		
		/* calculate parity (bit 1 of x) */
//		for(iii=1; iii<WTS_OSC_BIT_DEPTH; iii*=2)
//		{		
//			x_reg ^= x_reg >> iii;								
//		}
//		
		/* isolate parity bit and shift */
//		x_reg = ( PR_NOISE_LSB_MASK & x_reg ) << ( WTS_OSC_BIT_DEPTH - 1 );
//		
		/* shift to next state */
//		shift_reg = ( shift_reg >> 1 ) | x_reg ;
//		
//		*buffer++ =  (( (int16_t) shift_reg ) * amp);
//	}
}
/********************************************************************************************
 * 										get_sig_type										*
 ********************************************************************************************
 * 
 * check command line argument for a valid signal type
 * 
 * returns an integer value corresponding to the chosen signal type
 *
**/
sig_type wts_args::get_sig_type(const char * argv)
{
	if(argv == NULL)									return(WTS_INVALID);
	else if(!strcmp(argv, WTS_SIG_TYPE_SIN_WAVE))	return(WTS_SIN_WAVE);
	else if(!strcmp(argv, WTS_SIG_TYPE_FREQ_SWP))	return(WTS_FREQ_SWP);
	else if(!strcmp(argv, WTS_SIG_TYPE_PR_NOISE))	return(WTS_PR_NOISE);
	else											return(WTS_INVALID);
}

/********************************************************************************************
 * 										get_filename										*
 ********************************************************************************************
 *
 * check command line argument for valid output file name
 * 
 * the file name must end with .wav
 * 
 * returns NULL pointer if the filename is not valid
 * 
**/
char* wts_args::get_filename(char * argv)
{
	char *file_extension = argv + strlen(argv) - 4;
	
	if(argv == NULL)								return(NULL);
	else if(!strcmp(file_extension, ".wav"))		return(argv);
	else if(!strcmp(file_extension, ".WAV"))		return(argv);
	else if(!strcmp(file_extension, ".Wav"))		return(argv);
	else											return(NULL);
}

/********************************************************************************************
 * 										load_parameters										*
 ********************************************************************************************
 *
 * load parameter values from command line arguments
 * 
**/
int wts_args::load_parameters(int argc, char** argv)
{
	int iii;
	for(iii=3; iii<argc; iii++)
	{
		if(!strcmp(argv[iii], WTS_ARGS_SAMPLE_RATE))
		{
			if(++iii < argc)
				wts_args::sample_rate =	atof (argv[iii]);
		}
		else if(!strcmp(argv[iii], WTS_ARGS_FREQUENCY))
		{
			if(++iii < argc)
				wts_args::frequency =	atof (argv[iii]);
		}
		else if(!strcmp(argv[iii], WTS_ARGS_FREQ_LO))
		{
			if(++iii < argc)
				wts_args::freq_lo =		atof (argv[iii]);
		}
		else if(!strcmp(argv[iii], WTS_ARGS_FREQ_HI))
		{
			if(++iii < argc)
				wts_args::freq_hi =		atof (argv[iii]);
		}
		else if(!strcmp(argv[iii], WTS_ARGS_DURATION))
		{
			if(++iii < argc)
				wts_args::duration =	atof (argv[iii]);
		}
		else if(!strcmp(argv[iii], WTS_ARGS_GAIN))
		{
			if(++iii < argc)
				wts_args::gain =		atof (argv[iii]);
		}
		else return(iii);
	}
	return(0);
}

/********************************************************************************************
 * 										check_bounds										*
 ********************************************************************************************
 *
 * check loaded parameter values with min/max bounds
 * 
 * sine frequency can't exceed Nyquist, no negative frequencies
 * 
 * any invalid values will automatically be corrected
 * 
**/
void wts_args::check_bounds()
{
	if(wts_args::sample_rate > WTS_MAX_SAMPLE_RATE)
		wts_args::sample_rate = WTS_MAX_SAMPLE_RATE;
	
	if(wts_args::sample_rate < WTS_MIN_SAMPLE_RATE)
		wts_args::sample_rate = WTS_MIN_SAMPLE_RATE;
	
	if(wts_args::frequency > wts_args::sample_rate / 2)
		wts_args::frequency = wts_args::sample_rate / 2;
	
	if(wts_args::frequency < WTS_MIN_FREQUENCY) 
		wts_args::frequency = WTS_MIN_FREQUENCY;
	
	if(wts_args::freq_lo > wts_args::freq_hi)
		wts_args::freq_lo = wts_args::freq_hi;
	
	if(wts_args::freq_lo < WTS_MIN_FREQUENCY)
		wts_args::freq_lo = WTS_MIN_FREQUENCY;
	
	if(wts_args::freq_hi > wts_args::sample_rate / 2 )
		wts_args::freq_hi = wts_args::sample_rate / 2;
	
	if(wts_args::freq_hi < wts_args::freq_lo)
		wts_args::freq_hi = wts_args::freq_lo;
	
	if(wts_args::duration > WTS_MAX_DURATION)
		wts_args::duration = WTS_MAX_DURATION;
	
	if(wts_args::duration < WTS_MIN_DURATION)
		wts_args::duration = WTS_MIN_DURATION;
	
	if(wts_args::gain > WTS_MAX_GAIN)
		wts_args::gain = WTS_MAX_GAIN;
	
	if(wts_args::gain < WTS_MIN_GAIN)
		wts_args::gain = WTS_MIN_GAIN;
}

int main(int argc, char** argv)
{
	int err;
	test_sig *signal;
	
	if(argc < 3) {
		printf("Error - invalid syntax. Correct use:\n\twts [signal type] [file name].wav\n");
		return(-1);
	}
	
	wts_args::signal_type = wts_args::get_sig_type(argv[1]);
	if(wts_args::signal_type == WTS_INVALID) {
		printf("Error - invalid signal type: %s\n", argv[1]);
		return(-1);
	}
	
	wts_args::filename = wts_args::get_filename(argv[2]);
	if(wts_args::filename == NULL) {
		printf("Error - invalid filename: %s (must end with .wav)\n", argv[2]);
		return(-1);
	}
	
	err = wts_args::load_parameters(argc, argv);
	if(err) {
		printf("Error - invalid parameter argument: %s\n", argv[err]);
		return(-1);
	}
	
	wts_args::check_bounds();
	
	printf("sig type\t%d\nfilename\t%s\nsample rate\t%g\nfrequency\t%g\nfreq lo\t\t%g\nfreq hi\t\t%g\nduration\t%g\ngain\t\t%g\n", wts_args::signal_type, wts_args::filename,wts_args::sample_rate,wts_args::frequency,wts_args::freq_lo,wts_args::freq_hi,wts_args::duration,wts_args::gain);
	
	/* create generator for signal type */
	switch(wts_args::signal_type)
	{
		case WTS_SIN_WAVE:
			signal = new sin_wave(wts_args::sample_rate, wts_args::frequency, wts_args::gain);
			break;
		case WTS_FREQ_SWP:
			signal = new freq_swp(wts_args::sample_rate, wts_args::freq_lo, wts_args::freq_hi, wts_args::gain, wts_args::duration);
			break;
		case WTS_PR_NOISE:
			signal = new pr_noise(wts_args::gain);
			break;
	}
	
	wav_test_stream wts = wav_test_stream(signal, wts_args::filename);
	wts.write_file();
	
	return(0);
}
